#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "LOG.hpp"
#include <string.h>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
#include <functional>
#include <memory>
#include "InetAddr.h"
#include "Comm.hpp"

// enum
// {
//     SOCKET_ERROR = 0,
//     BIND_ERROR,
//     USEGE_ERROR,
//     READ_ERROR,
//     LISTEN_ERROR,
//     ACCEPT_ERROR,
//     CONNECT_ERROR
// };
class Socket;
using socket_sptr = std::shared_ptr<Socket>;

// 模板方法模式
class Socket
{

public:
    Socket()
    {
    }
    virtual void CreatSocketOrDie() = 0;
    virtual void BindSocketOrDie(InetAddr &addr) = 0;
    virtual void ListenSocketOrDie() = 0;
    //virtual socket_sptr AcceptSocket(InetAddr *addr) = 0;
    virtual int AcceptSocket(InetAddr *addr,int* errcode) = 0;
    virtual bool ConnectSocket(InetAddr &addr) = 0;
    virtual int sockfd() = 0;
    virtual void SetSocketAddrReuse() = 0;
    virtual int Recv(string *out) = 0;
    virtual int Send(string &in) = 0;
    virtual void Close() = 0;

public:
    void BuildListenSocket(InetAddr &addr)
    {
        CreatSocketOrDie();
        SetSocketAddrReuse();
        BindSocketOrDie(addr);
        ListenSocketOrDie();
    }
    bool BuildClientSocket(InetAddr &addr)
    {
        CreatSocketOrDie();
        return ConnectSocket(addr);
    }

    ~Socket()
    {
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket(int socket = -1)
        : _socket(socket)
    {
    }
    void CreatSocketOrDie()
    {
        _socket = socket(AF_INET, SOCK_STREAM, 0);
        if (_socket < 0)
        {
            LOG(FATAL, "_socket fail");
            exit(SOCKET_ERROR);
        }
        SetNonBlock(_socket);
        LOG(INFO, "_socket ET success: %d", _socket);
    }
    void BindSocketOrDie(InetAddr &addr)
    {
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());
        local.sin_port = htons(addr.Port());
        int n = bind(_socket, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind fail");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success");
    }
    void ListenSocketOrDie()
    {
        int n = listen(_socket, 0);
        if (n < 0)
        {
            LOG(FATAL, "listen fail");
            exit(LISTEN_ERROR);
        }
    }
    //socket_sptr AcceptSocket(InetAddr *addr)
    int AcceptSocket(InetAddr *addr,int* errcode)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int socket = accept(_socket, (struct sockaddr *)&peer, &len);
        *errcode = errno;
        if (socket < 0)
        {
            LOG(WARNING, "accept fail");
            return -1;
        }
        *addr = peer;
        //socket_sptr result = make_shared<TcpSocket>(socket);
        //return result;
        LOG(INFO, "accept ET success:%d", socket);
        SetNonBlock(socket);
    
        return socket;
    }
    bool ConnectSocket(InetAddr &addr)
    {
        struct sockaddr_in sever;
        bzero(&sever, sizeof(sever));
        sever.sin_family = AF_INET;
        sever.sin_port = htons(addr.Port());
        sever.sin_addr.s_addr = inet_addr(addr.Ip().c_str());

        // 和服务器sever建立连接
        int n = connect(_socket, (struct sockaddr *)&sever, sizeof(sever));
        if (n < 0)
        {
            LOG(FATAL, "connect error");
            return false;
        }
        return true;
    }
    int sockfd()
    {
        return _socket;
    }

    void SetSocketAddrReuse()
    {
        int opt = 1;
        setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    }

    int Recv(string *out)
    {
        char commend[1024];
        ssize_t n = recv(_socket, commend, sizeof(commend), 0);
        if (n > 0)
        {
            // 证明收到消息
            commend[n] = 0;
            *out += commend;
        }
        return n;
    }
    int Send(string &in)
    {
        ssize_t n = send(_socket, in.c_str(), in.size(), 0);
        return n;
    }
    void Close()
    {
        if (_socket > -1)
            close(_socket);
    }
    ~TcpSocket()
    {
    }

private:
    int _socket;
};
